package cn.vire.electric.simulation.netty.server;

import io.netty.bootstrap.Bootstrap;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBufUtil;
import io.netty.channel.*;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.DatagramPacket;
import io.netty.channel.socket.nio.NioDatagramChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.LineBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.timeout.IdleStateHandler;
import io.netty.util.concurrent.GlobalEventExecutor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;

import java.net.*;
import java.nio.CharBuffer;
import java.nio.charset.Charset;

//@Component
@Slf4j
//@Order(10001)
public class NettyApplication implements ApplicationRunner {

    @Value("${netty.server.part:9090}")
    private int nettyServerPort;
    //用于存放用户Channel信息，也可以建立map结构模拟不同的消息群
    public static ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
    Channel broadcastClient;

    @Override
    public void run(ApplicationArguments args) throws Exception {
        new Thread(new Runnable() {//不要用线程池,因为方法是阻塞的,会占用线程池线程,可能导致线程池中的任务一值在队列里等待
            @Override
            public void run() {
                try {
                    NettyApplication.this.run0();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    NettyApplication.this.run1();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    NettyApplication.this.run2();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }

    public void run1() throws Exception {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        try {
            Bootstrap b = new Bootstrap();
            b.group(bossGroup)
                    .channel(NioDatagramChannel.class)
                    .handler(new ChannelInitializer<NioDatagramChannel>() {
                        @Override
                        protected void initChannel(NioDatagramChannel channel) throws Exception {
                            channel.pipeline().addLast(
                                    new ChannelDuplexHandler() {
                                        InetSocketAddress remoteAddr = null;
                                        InetSocketAddress localAddress = null;
                                        @Override
                                        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                                            DatagramPacket msg1 = (DatagramPacket) msg;
                                            this.remoteAddr = msg1.sender();
                                            this.localAddress = msg1.recipient();
                                            String content = msg1.content().toString(Charset.forName("utf-8"));
                                            ctx.fireChannelRead(content);
                                        }

                                    },
                                    new SimpleChannelInboundHandler<String>(){
                                        @Override
                                        protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
                                            log.info("udp服务收到数据[{}]",msg);
                                            SocketAddress socketAddress = ctx.channel().remoteAddress();
                                            channelGroup.writeAndFlush("通过广播收到:" + msg);
                                        }
                                    });
                        }

                    })
                    .option(ChannelOption.SO_BROADCAST, true)
                    .option(ChannelOption.SO_SNDBUF, 1024*1024)
                    .option(ChannelOption.SO_RCVBUF, 1024*1024)
            ;

            // Bind and start to accept incoming connections.
            ChannelFuture f = b.bind(91).sync(); // (7)
            log.info("upd服务监听中...");
            f.channel().closeFuture().sync();
            log.info("upd服务关闭...");
        } finally {
            bossGroup.shutdownGracefully();
        }
    }
    public void run2() throws Exception {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        try {
            Bootstrap b = new Bootstrap();
            b.group(bossGroup)
                    .channel(NioDatagramChannel.class)
                    .handler(new ChannelInitializer<NioDatagramChannel>() {
                        @Override
                        protected void initChannel(NioDatagramChannel channel) throws Exception {
                            channel.pipeline().addLast(
                                    new ChannelDuplexHandler() {
                                        InetSocketAddress remoteAddr = null;
                                        InetSocketAddress localAddress = null;
                                        @Override
                                        public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
                                            InetSocketAddress remoteAddr1 = this.remoteAddr;
                                            InetSocketAddress remoteAddr2 = this.remoteAddr;
                                            NetworkInterface inetAddress = NetworkInterface.getByInetAddress(InetAddress.getByName("10.19.182.43"));
                                            for (InterfaceAddress interfaceAddress : inetAddress.getInterfaceAddresses()) {
                                                if (interfaceAddress.getBroadcast()!=null) {
                                                    remoteAddr1 = new InetSocketAddress(interfaceAddress.getBroadcast(), 91);
                                                    remoteAddr2 = new InetSocketAddress(interfaceAddress.getBroadcast(), 92);
                                                }
                                            }
                                            DatagramPacket datagramPacket1 = new DatagramPacket(ByteBufUtil.encodeString(ctx.alloc(), CharBuffer.wrap((String) msg), Charset.forName("UTF-8")), remoteAddr1, localAddress);
                                            DatagramPacket datagramPacket2 = new DatagramPacket(ByteBufUtil.encodeString(ctx.alloc(), CharBuffer.wrap((String) msg), Charset.forName("UTF-8")), remoteAddr2, localAddress);
                                            ctx.write(datagramPacket1,promise);
                                            ctx.write(datagramPacket2,promise);
                                            ctx.flush();
                                        }

                                    });
                        }

                    })
                    .option(ChannelOption.SO_BROADCAST, true)
                    .option(ChannelOption.SO_SNDBUF, 1024*1024)
                    .option(ChannelOption.SO_RCVBUF, 1024*1024)
            ;

            // Bind and start to accept incoming connections.
            ChannelFuture f = b.bind(0);
            broadcastClient = f.channel();
            f.sync();
            f.channel().closeFuture().sync();
            log.info("upd客户端服务关闭...");
        }finally {
            bossGroup.shutdownGracefully();
        }
    }

    public void run0() throws Exception {
        /*
        NioEventLoopGroup是一个处理I/O操作的多线程事件循环。Netty为不同类型的传输提供了各种EventLoopGroup实现。
        在本例中，我们实现了一个服务器端应用程序，因此将使用两个NioEventLoopGroup。
        第一个，通常被称为“老板”，接受一个传入的连接。第二个，通常被称为“worker”，一旦老板接受了连接，处理已接受的连接的流量，并将已接受的连接注册给worker。
        使用多少线程以及它们如何映射到创建的通道取决于EventLoopGroup实现，甚至可以通过构造函数进行配置。
         */
        EventLoopGroup bossGroup = new NioEventLoopGroup(); // (1)
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            /*
            ServerBootstrap是一个设置服务器的助手类。您可以直接使用Channel设置服务器。但是，请注意，这是一个冗长的过程，在大多数情况下您不需要这样做。
             */
            ServerBootstrap b = new ServerBootstrap(); // (2)
            b.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class) // 这里，我们指定使用NioServerSocketChannel类，它用于实例化一个新Channel以接受传入的连接。
                    .handler(new LoggingHandler(LogLevel.INFO))
                    .childHandler(new ChannelInitializer() {
                        /*
                        这里指定的处理程序总是由一个新接受的Channel求值。ChannelInitializer是一个特殊的处理程序，用于帮助用户配置新Channel。
                        最有可能的情况是，您希望通过添加一些处理程序(如DiscardServerHandler)来配置新Channel的ChannelPipeline，以实现您的网络应用程序。
                        随着应用程序变得越来越复杂，您可能会向管道中添加更多的处理程序，并最终将这个匿名类提取到顶级类中。
                         */
                        @Override
                        protected void initChannel(Channel channel) throws Exception {
                            channel.pipeline().addLast(
//                                    new LoggingHandler(LogLevel.INFO),
                                    new IdleStateHandler(300, 150, 0),
//                                    new JsonObjectDecoder(),
                                    new LineBasedFrameDecoder(1024),
                                    new StringEncoder(Charset.forName("UTF-8")),
                                    new StringDecoder(Charset.forName("UTF-8")),
                                    new DiscardServerHandler(channelGroup,broadcastClient));

                        }
                    })
                    /*
                    您还可以设置特定于Channel实现的参数。我们正在编写一个TCP/IP服务器，因此我们可以设置套接字选项，如tcpNoDelay和keepAlive。
                    请参考ChannelOption的apidocs和特定的ChannelConfig实现，以获得关于支持的ChannelOptions的概述。
                     */
                    .option(ChannelOption.SO_BACKLOG, 128)
                    /*
                     你注意到option()和childOption()了吗?option()是用于接受传入连接的NioServerSocketChannel的。childOption()用于父服务器通道(在本例中是NioSocketChannel)所接受的通道。
                     */
                    .childOption(ChannelOption.SO_KEEPALIVE, true)
                    .childOption(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000)
            ; // (6)

            // Bind and start to accept incoming connections.
            /*
            我们准备好出发了。剩下的就是绑定到端口并启动服务器。在这里，我们绑定到机器中所有网卡(网络接口卡)的端口8080。现在可以任意多次调用bind()方法(使用不同的绑定地址)。
             */
            ChannelFuture f = b.bind(nettyServerPort); // (7)
            f.sync();
            // Wait until the server socket is closed.
            // In this example, this does not happen, but you can do that to gracefully
            // shut down your server.
            f.channel().closeFuture().sync();
        } finally {
            workerGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
        }
    }

    public static void main(String[] args) {
        System.out.println(System.getProperty("io.netty.allocator.type"));
    }
}
